#include <vector>
#include <glm/glm.hpp>
#include <GL/glew.h>
#include <GL/glfw.h>

#include "SimulationManager.h"
#include "lbcuda.h"


#define WINDOW_W 600
#define WINDOW_H 600



// 

GLuint partsVBO;

float ts = 0.03;

float cpuWeight[9] = {
		4.0f/9.0f,

		1.0f/9.0f,
		1.0f/9.0f,
		1.0f/9.0f,
		1.0f/9.0f,

		1.0f/36.0f,
		1.0f/36.0f,
		1.0f/36.0f,
		1.0f/36.0f
	};


	int cpuE[18] = {
		0,0,1, 0,-1,1, 1,-1,-1, //x
		0,1,0,-1, 0,1,-1,-1, 1  //y
	};


//A global simulation manager.
//This takes care of most boiler-plate code.
//If you only want to draw something, you only need to do three things:
//1) Create a display func with the function signerature
//	void funcname()
//2) In main, call 
//	sm.SetCallbackDisplay(funcname)
//  where funcname is the name of your draw function.
//3) In main, after the above function call, call
//	sm.Run();
//  This will start running the simulation.
// 
//  For a list of the callbacks the user can set, look at the "SetCallback..." functions 
//  in the SimulationManager header file.
//  Also, the GetCamera() method will get the camera and allow you to call methods which move
//	around the scene or rotate, etc.
SimulationManager sm(WINDOW_W, WINDOW_H);

#define NUMCELLSU 200
#define NUMCELLSV 200
#define NUMCELLS NUMCELLSU*NUMCELLSV

// PARTS INFO
int NUMPARTSU = 4000;
int NUMPARTSV = 4000;
int NUMPARTS = NUMPARTSU*NUMPARTSV;
glm::vec2 lowerpc = glm::vec2(NUMCELLSU*0.01f, NUMCELLSU*0.01F);
glm::vec2 upperpc = glm::vec2(NUMCELLSV*0.9f, NUMCELLSV*0.9F);
glm::vec2 stepsize = glm::vec2((upperpc-lowerpc).x/(float)NUMPARTSU, (upperpc-lowerpc).y/(float)NUMPARTSV);



CudaLB * clb;
float lbgrid[NUMCELLSU*NUMCELLSV*9];

void InitParts() {
	std::vector<float> parts;
	parts.resize(NUMPARTS*2);
	
	int partnum = 0;
	for (float i = lowerpc.x; i < upperpc.x; i += stepsize.x){
		for (float j = lowerpc.y; j < upperpc.y; j += stepsize.y){
			if (partnum < NUMPARTS){

				parts[partnum*2] = i;
				parts[partnum*2+1] = j;
			}

			partnum++;
		}
	}

	// Init the cuda size
	clb->SetNumParts(NUMPARTS, 2, &parts[0]);
}


glm::vec2 GetVel( int cndx ){
	glm::vec2 sum;
	for (int i = 0; i < 9; ++i){
		sum += lbgrid[cndx*9+i]*glm::vec2(cpuE[i], cpuE[i+9]);
	}

	return sum;
}

void DrawVels() {
	glm::vec2 vel;
	glBegin(GL_LINES);
	for (int u = 0; u < NUMCELLSU; ++u){
		for (int v = 0; v < NUMCELLSV; ++v){
			
			vel = GetVel(v*NUMCELLSV + u);
			glVertex2f(u, v);
			glVertex2f((int)u+vel.x*100.0f, (int)v+vel.y*100.0f);
		}
	}
	glEnd();
}

struct MouseState{ 
	MouseState() { isPressed = false; }
	glm::ivec2 last;
	bool isPressed;
} mouseState;

glm::ivec2 GetMouseCell(int mx, int my){
	glm::vec2 prop = glm::vec2( (float)mx / (float) WINDOW_W,
		(float)my / (float)WINDOW_H);

	return glm::ivec2(prop.x * NUMCELLSU, prop.y * NUMCELLSV);
}


float supportRad = 10;


void AddVelocity(glm::ivec2 & cell, glm::vec2 & inVel){

	float velMag = glm::length(inVel);
	glm::ivec2 tmp;
	for (int u = -supportRad; u < supportRad; ++u){
		for (int v = -supportRad; v < supportRad; ++v){
			if (u<0||u>=NUMCELLSU){continue;}
			if (v<0||v>=NUMCELLSV){continue;}
			tmp = glm::ivec2(cell.x+u, cell.y+v);
			int cndx = tmp.y*NUMCELLSU + tmp.x;
			for (int i = 0; i < 9; ++i){
				float dot = cpuE[i]*inVel.x + cpuE[i+9]*inVel.y;//glm::dot((cpuE[i]), inVel);
				lbgrid[cndx*9 + i] += dot*velMag*(velMag/(supportRad*2));
			}
		}
	}

	clb->SetGrid(lbgrid);
}


void ProcessMouse () {
	glm::ivec2 mp;
	glfwGetMousePos(&mp.x, &mp.y);

	if (glfwGetMouseButton( GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS){
		glm::ivec2 cell = GetMouseCell(mp.x, WINDOW_H - mp.y);

		glm::vec2 vel = glm::vec2(
			(float)cell.x-mouseState.last.x,
			(float)cell.y-mouseState.last.y);
		float lenvel = glm::length(vel);

		if (lenvel != 0){
			vel /= glm::length(vel);
			vel *= 0.8f;
		
			if (mouseState.isPressed){
				AddVelocity(cell, vel);
			}
		}

		if (mouseState.last != cell){
			mouseState.last = cell;
		}

		mouseState.isPressed = true;
	} 

	else {
		mouseState.isPressed = false;
	}
}

void DrawParts() {
	GLuint vbo = clb->GetPartsVBO();

	//Bind and enable vertex array
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glEnableClientState(GL_VERTEX_ARRAY);

	// set the pointer to read from the bound buffer.
	glVertexPointer(2, GL_FLOAT, 0, 0);

	glColor3f(0.0, 1.0f, 0.0f);
	glDrawArrays(GL_POINTS, 0, NUMPARTS);
	
	glDisableClientState(GL_VERTEX_ARRAY);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

}

//Display
//This function will be called as fast as possible, unlike the Update func.
//Only GL display code should be put in this section.  The screen
//is automatically cleared to white in the InitFrames() func, and the 
//glfwSwapBuffers() is called by the default EndFrames() function, so you only
//have to worry about actual drawing here.
void Display() {

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1, NUMCELLSU+1, -1, NUMCELLSV+1, 0.01, 10 );

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glTranslatef(0,0,-1);

	//DrawVels();

	DrawParts();
}

//Update
//This function will be called every timestep (ts).  The timestep can be
//set via the SetTimeStep method in SimulationManager. Put all non-drawing code
//here.
void KeyboardFunc( int key, int action ){

}


void Update(float ts) {
	//XXX
	//All update code goes here
	//...
	// Set

	if (clb->IsAllocated()){

		clb->Update();
		clb->UpdateParts();
		clb->GetGrid(lbgrid);
	}


	ProcessMouse();

}

void Init(){

	InitCudaGLDevice();

	// Init GLEW
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
	  /* Problem: glewInit failed, something is seriously wrong. */
	  fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
		return;
	}
	fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));

	clb = new CudaLB(NUMCELLSU, NUMCELLSV);
	
	InitParts();

	printf("Number particles: %d\n", NUMPARTSU*NUMPARTSV);
	printf("Number cells: %d\n", NUMCELLS);

	sm.SetTimeStep(ts);
}

int main (int argc, char ** argv ){


	sm.SetCallbackDisplay(Display);
	sm.SetCallbackUpdate(Update);
	sm.SetCallbackLoadResources(Init);



	//XXX

	//Start simulation.
	sm.Run();

	return 0;
}
